package com.binary.coder.gift.core.common.excel;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import com.alibaba.excel.util.DateUtils;
import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder;
import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.handler.WriteHandler;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.alibaba.excel.write.style.column.AbstractColumnWidthStyleStrategy;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.HorizontalAlignment;

import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
public class ExcelExporter {

    protected BufferedOutputStream bufferedOutputStream;
    protected ExcelWriter writer;
    protected WriteSheet sheet;
    @Getter
    protected int pageSize = 20;

    protected String name;
    protected List<String> header;
    protected int headerLine = 1;
    protected List<List<String>> headerList;

    protected List<Converter> converterList = new ArrayList<>();
    protected List<WriteHandler> writeHandlerList = new ArrayList<>();

    public static ExcelExporter create() {
        return new ExcelExporter();
    }

    public ExcelExporter setPageSize(int pageSize) {
        this.pageSize = pageSize;
        return this;
    }

    ///////初始化/////////////

    public ExcelExporter init(OutputStream outputStream, String name, List<String> header) {
        return init(outputStream, name, header, null);
    }

    public ExcelExporter init(OutputStream outputStream, String name, List<String> header, Map<String, String> headerAlias) {
        this.bufferedOutputStream = new BufferedOutputStream(outputStream);
        this.header = header;
        this.headerList = convertHeader(header, headerAlias);
        this.headerLine = this.headerList.stream().map(t -> t.size()).max((a, b) -> a - b).get();
        this.name = name;
        return this;
    }

    public ExcelExporter addConverter(Converter converter) {
        converterList.add(converter);
        return this;
    }

    public ExcelExporter initDefaultConverter() {
        converterList.add(new ObjectStringConverter());
        converterList.add(new TimeStringConverter());
        converterList.add(new TimestampStringConverter());
        return this;
    }

    public ExcelExporter addWriteHandler(WriteHandler handler) {
        writeHandlerList.add(handler);
        return this;
    }

    public ExcelExporter initDefaultWriteHandler() {
        writeHandlerList.add(setExcelStyle());
        writeHandlerList.add(new Custemhandler());
        // 注意线构建header
        writeHandlerList.add(new CustomSheetWriteHandler(this.headerLine));
        return this;
    }

    ///////构建/////////////
    public ExcelExporter buildWriter() {
        this.writer = EasyExcel.write(this.bufferedOutputStream).build();
        ExcelWriterSheetBuilder builder = EasyExcel.writerSheet(this.name).head(this.headerList);
        this.converterList.forEach(t -> builder.registerConverter(t));
        this.writeHandlerList.forEach(t -> builder.registerWriteHandler(t));
        this.sheet = builder.build();
        return this;
    }

    /////////////////////第一种，把自己传给外部，由外部调度，不停循环塞数据过来/////////////////////////////////////
    public ExcelExporter writeExcelData(List<Map<String, Object>> dataList) {
        try {
            writer.write(convertList(dataList, header), sheet);

        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return this;
    }

    public ExcelExporter writeExcelEnd(List<Map<String, Object>> dataList) {
        try {
            if (dataList != null) {
                writer.write(convertList(dataList, header), sheet);
            }
            writer.finish();
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            if (bufferedOutputStream != null) {
                try {
                    bufferedOutputStream.close();
                } catch (IOException e) {
                }
            }
        }
        return this;
    }


    /////////////////////第二种，传入dataMaker，由内部调度，不停循环主动读取数据/////////////////////////////////////
    public ExcelExporter writeExcelData(ExcelDataMaker dataMaker) {
        int index = 0;
        List<Map<String, Object>> dataList = null;
        try {
            do {
                dataList = dataMaker.getData(index++, pageSize);
                if (dataList != null && dataList.size() > 0) {
                    writer.write(convertList(dataList, header), sheet);
                } else {
                    break;
                }
            } while (true);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            writer.finish();
            if (bufferedOutputStream != null) {
                try {
                    bufferedOutputStream.close();
                } catch (IOException e) {
                }
            }
        }
        return this;
    }


    public List<List<String>> convertHeader(List<String> header, Map<String, String> headerAlias) {
        //   int size = headerAlias.values().stream().map(v -> v.split("\\.").length).max((a, b) -> a - b).get();
        return header.stream().map(t -> {
            List<String> list = new ArrayList<>(headerLine);
            if (headerAlias == null) {
                list.addAll(Arrays.asList(t.split("\\.")));
                return list;
            }
            String name = headerAlias.get(t);
            if (name == null) {
                list.addAll(Arrays.asList(t.split("\\.")));
                return list;
            }
            list.addAll(Arrays.asList(name.split("\\.")));
            return list;
        }).collect(Collectors.toList());
    }

    public List<List<Object>> convertList(List<Map<String, Object>> dataList, List<String> excelHeaders) throws Exception {
        List<List<Object>> lists = new ArrayList<>();
        for (Map<String, Object> bean : dataList) {
            List<Object> data = new ArrayList<>();
            for (String excelHeader : excelHeaders) {
                Object value = bean.get(excelHeader);
                if (value != null) {
                    data.add(value);
                } else {
                    String[] hs = excelHeader.split("\\.");
                    if (hs.length == 1) {
                        data.add("");// 空
                    } else {
                        // 根据分割取值
                        Map<String, Object> map = bean;
                        for (int i = 0; i < hs.length - 1 && map != null; i++) {
                            map = (Map<String, Object>) map.get(hs[i]);
                        }

                        if (map == null) {
                            data.add("");// 空
                        } else {
                            data.add(map.getOrDefault(hs[hs.length - 1], ""));// 空
                        }
                    }
                }
            }
            lists.add(data);
        }
        return lists;
    }


    /**
     * 设置excel样式
     *
     * @return
     */
    public HorizontalCellStyleStrategy setExcelStyle() {
        // 表头策略
        WriteCellStyle headWriteCellStyle = new WriteCellStyle();
        WriteFont writeFont = new WriteFont();
        writeFont.setFontName("宋体");
        writeFont.setFontHeightInPoints((short) 11);
        //加粗
        writeFont.setBold(true);
        headWriteCellStyle.setWriteFont(writeFont);
        //内容样式策略
        WriteCellStyle contextWriteeCellStyle = new WriteCellStyle();
        //设置水平居中
        contextWriteeCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        // 这个策略是 头是头的样式 内容是内容的样式 其他的策略可以自己实现
        HorizontalCellStyleStrategy horizontalCellStyleStrategy =
                new HorizontalCellStyleStrategy(headWriteCellStyle, contextWriteeCellStyle);
        return horizontalCellStyleStrategy;
    }

    /**
     * easyexcel提供的自动列宽不能满足需求，只能自己根据业务要求重写
     *
     * @description: EasyExcel设置列宽
     **/
    public static class Custemhandler extends AbstractColumnWidthStyleStrategy {
        public static final int MAX_COLUMN_WIDTH = 50;

        @Override
        protected void setColumnWidth(WriteSheetHolder writeSheetHolder, List<WriteCellData<?>> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
            int columnWidth = 20;
            if (isHead) {
                writeSheetHolder.getSheet().setColumnWidth(cell.getColumnIndex(), columnWidth * 256);

            }
        }
    }

    public interface ExcelDataMaker {
        List<Map<String, Object>> getData(int index, int size);
    }

    public static class CustomSheetWriteHandler implements SheetWriteHandler {
        int headerLine;

        CustomSheetWriteHandler(int headerLine) {

            this.headerLine = headerLine;
        }

        @Override
        public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {

        }

        @Override
        public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
            //固定表头
            writeSheetHolder.getSheet().createFreezePane(0, headerLine, 0, headerLine);
        }
    }


    public static class TimestampStringConverter implements Converter<Timestamp> {
        @Override
        public Class supportJavaTypeKey() {
            return Timestamp.class;
        }

        @Override

        public CellDataTypeEnum supportExcelTypeKey() {
            return CellDataTypeEnum.STRING;
        }

        @Override
        public WriteCellData convertToExcelData(Timestamp value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
            if (contentProperty == null || contentProperty.getDateTimeFormatProperty() == null) {
                return new WriteCellData(DateUtils.format(value, null));
            } else {
                return new WriteCellData(DateUtils.format(value, contentProperty.getDateTimeFormatProperty().getFormat()));
            }
        }
    }


    public static class TimeStringConverter implements Converter<Time> {

        @Override
        public Class supportJavaTypeKey() {
            return Time.class;
        }

        @Override

        public CellDataTypeEnum supportExcelTypeKey() {
            return CellDataTypeEnum.STRING;
        }

        @Override
        public WriteCellData convertToExcelData(Time value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
            if (contentProperty == null || contentProperty.getDateTimeFormatProperty() == null) {
                return new WriteCellData(DateUtils.format(value, null));
            } else {
                return new WriteCellData(DateUtils.format(value, contentProperty.getDateTimeFormatProperty().getFormat()));
            }
        }
    }

    public static class ObjectStringConverter implements Converter<Object> {

        @Override
        public Class supportJavaTypeKey() {
            return Object.class;
        }

        @Override

        public CellDataTypeEnum supportExcelTypeKey() {
            return CellDataTypeEnum.STRING;
        }


        @Override
        public WriteCellData convertToExcelData(Object value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {

            if (value == null) {
                return new WriteCellData("");
            } else if (value instanceof Date) {
                if (contentProperty == null || contentProperty.getDateTimeFormatProperty() == null) {
                    return new WriteCellData(DateUtils.format((Date) value, null));
                } else {
                    return new WriteCellData(DateUtils.format((Date) value, contentProperty.getDateTimeFormatProperty().getFormat()));
                }
            } else {
                return new WriteCellData(value.toString());
            }

        }
    }
}
